<template>
	<view class="vi_timeline vi_clearFix">
		<!-- 轴线 -->
		<view class="vi_timeline_line" :class="'vi_timeline_' +setting.mode" :style="lineStyle"></view>
		<!-- 组 -->
		<view class="vi_timeline_group" :class="'vi_timeline_' +setting.mode" :style="groupStyle" v-for="(group, index) in inList" :key="index">
			<view class="vi_timeline_groupHead" v-if="group._name" @click="groupClick(group)"
				:class="{'vi_timeline_overflow':group._len>=setting.lineSpace*2}"
				:style="{
					'left': setting.mode=='left'? -setting.lineSpace*(group._len>=setting.lineSpace*2?2:1)+'em' :'initial',
					'right': setting.mode=='right'? -setting.lineSpace*(group._len>=setting.lineSpace*2?2:1)+'em' :'initial',
					'margin-bottom': setting.rowSpace+'em',
				}">
				<vi-style-controller
					:type="group.type||groupType||setting.type"
					:color="group.color||groupColor||color"
					:textColor="group.textColor||groupTextColor||textColor"
					:radius="group.radius||setting.groupRadius"
					:plain="group.plain"
					inline
					active>
					<view class="vi_timeline_groupHead_text">
						{{group._name}}
					</view>
				</vi-style-controller>
			</view>
			<view class="vi_timeline_row" :style="rowStyle" v-for="(item, idx) in group._children" :key="idx"
				:class="{
					'vi_acrossLeft': setting.mode=='across'&&!item._align,
					'vi_acrossRight': setting.mode=='across'&&item._align,
				}">
				<!-- 节点图标 -->
				<view class="vi_timeline_rowIcon"
					:style="{
						'left': setting.mode=='left'||(setting.mode=='across'&&item._align)? -setting.lineSpace+'em' :'initial',
						'right': setting.mode=='right'||(setting.mode=='across'&&!item._align)? -setting.lineSpace+'em' :'initial',
						'background-color': setting.iconBgColor
					}">
					<vi-style-controller
						:type="item.type||setting.type"
						:color="item.color||color"
						:textColor="item.textColor||textColor"
						:plain="item.plain"
						:radius="item.radius||'circle'"
						width="1.2em"
						height="1.2em"
						active>
						<view class="iconfont" :class="item.icon||iconSetting.node"></view>
					</vi-style-controller>
				</view>
				<!-- 对侧时间 -->
				<view class="vi_timeline_aside" v-if="timeAside" @click="itemClick(item)"
					:style="{
						'left': setting.mode=='left'||(setting.mode=='across'&&item._align)? -setting.lineSpace*2+'em' :'initial',
						'right': setting.mode=='right'||(setting.mode=='across'&&!item._align)? -setting.lineSpace*2+'em' :'initial',
						'width': setting.asideWidth +'em'
					}">
					<text class="vi_timeline_text">{{dateFormat(item[keySetting.itemTime], timeFormat)}}</text>
				</view>
				<!-- 内容模板 -->
				<view class="vi_timeline_content">
					<tpl-common v-if="setting.template=='common'"
						:templateSet="templateSet"
						:type="item.type||setting.type"
						:color="item.color||color"
						:textColor="item.textColor||textColor"
						:dataSet="item"
						:keys="keys"
						:mode="setting.mode"
						:hideTime="timeAside"
						:timeFormat="timeFormat"
						@event="onEvent"></tpl-common>
					<tpl-card v-else-if="setting.template=='card'"
						:templateSet="templateSet"
						:type="item.type||setting.type"
						:color="item.color||color"
						:textColor="item.textColor||textColor"
						:dataSet="item"
						:keys="keys"
						:mode="setting.mode"
						:hideTime="timeAside"
						:timeFormat="timeFormat"
						@event="onEvent"></tpl-card>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	//由于小程序不支持作用域插槽也不支持动态组件，目前只能预定义模板进行插入。使用slot将无法得到当前行数据
	/*添加自定义模板注意：
		1、import引入自定义模板组件
		2、components增加模板组件名
		3、vi_timeline_row中增加模板组件的定义
	*/
	import tplCommon from "./tpl-common.vue"
	import tplCard from "./tpl-card.vue"
	export default {
		name:"viTimeline",
		components:{
			tplCommon,
			tplCard
		},
		props:{
			//全局主题色，节点设置优先
			type:{
				type:[String,null],
				default:null
			},
			//全局自定义主题色，节点设置优先
			color:{
				type:[String,null],
				default:""
			},
			//全局固定色，节点设置优先。控制组节点
			textColor:{
				type:[String,null],
				default:""
			},
			//内容显示模式，left线在左侧、right线在右侧、across内容交错两侧
			mode:{
				type:[String,null],
				default:""
			},
			//使用组，开启与否取决于数据集是否带有组结构
			useGroup:{
				type:[Boolean,null],
				default:false
			},
			//插入组节点，分隔上下时间区域。
			//预定义类型，按类型自动插入，无视数据集上的组设置。year年、month月、day日、hour时。单位不能小于atom，小于等于则关闭。
			//自定义类型custom，使用数据集上的组数据显示。如果未开启useGroup则不能使用
			groupMode:{
				type:[Boolean,String,null],
				default:false
			},
			//组节点主题色
			groupType:{
				type:[String,null],
				default:""
			},
			//组节点自定义主题色
			groupColor:{
				type:[String,null],
				default:""
			},
			//组节点固定文字色
			groupTextColor:{
				type:[String,null],
				default:""
			},
			//组节点圆角，预定义类型时默认圆形。自定义类型时默认圆角方形
			groupRadius:{
				type:[String,Number,Array,null],
				default:null
			},
			//组节点镂空
			groupPlain:{
				type:[Boolean,null],
				default:false
			},
			//粒度，节点的基础时间单位，影响显示
			//可选year年、month月、day日、hour时、minute分、second秒
			//在可选值外则为自定义时间格式
			atom:{
				type:[String,null],
				default:""
			},
			//节点排序。asc升序，desc降序，为空不排序，使用自定义组节点时不排序。使用预定义组节点时必须排序，默认升序
			sort:{
				type:[String,null],
				default:""
			},
			//数据集
			dataList:{
				type:[Array,null],
				default:()=>[]
			},
			//线与左右内容距离，等于留给图标宽度，固定单位em
			lineSpace:{
				type:[Number,null],
				default:null
			},
			//内容行间距，固定单位em
			rowSpace:{
				type:[Number,null],
				default:null
			},
			//时间置于对侧，否时时间在同侧内容区
			timeAside:{
				type:[Boolean,null],
				default:false
			},
			//对侧宽度，开启对侧时有效，固定单位em。必填
			asideWidth:{
				type:[Number,null],
				default:0
			},
			//数据键名
			keys:{
				type:[Object,null],
				default:()=>({})
			},
			//图标替换
			icon:{
				type:[Object,null],
				default:()=>({})
			},
			//模板名
			template:{
				type:[String,null],
				default:""
			},
			//模板设置，向内传递
			templateSet:{
				type:[Object,null],
				default:()=>({})
			},
			//图标背景色，用于图标边缘覆盖轴线，应等于外部背景
			iconBgColor:{
				type:[String,null],
				default:""
			},
			//组节点显示格式，预定义组节点用
			groupFormat:{
				type:[Object,null],
				default:()=>({})
			},
			nodeFormat:{
				type:[Object,null],
				default:()=>({})
			}
		},
		data() {
			return {
				inList:null,
				dicGroupFormat:{
					year: "YYYY",
					month: "MM",
					day: "DD",
					hour: "h:00"
				},
				dicNodeFormat:{
					year: "YYYY",
					month: "YYYY-MM",
					day: "MM-DD",
					hour: "h:00",
					minute: "hh:mm",
					second: "hh:mm:ss",
					datetime: "YYYY-MM-DD hh:mm"
				},
				dicKeys:{
					groupName: "name",			//组标题键名，useGroup以及groupMode为custom时被使用。内部使用_name
					groupChildren: "children",	//节点列表所在键名，不能为空。内部使用_children
					itemName: "name",			//节点标题键名。内部使用_name
					itemTime: "time",			//节点时间键名。内部使用_time
				},
				dicIcon:{
					node:"icon-point",
				},
				lineTop:0,		//轴线起始点
				lineBottom:0,	//轴线结束点
			}
		},
		computed: {
			setting(){
				return {
					type: this.$api.isDefined(this.type)? this.type :"primary",
					mode: this.mode ||"left",
					groupRadius: this.$api.isDefined(this.groupRadius)? this.groupRadius :"0.25em",
					lineSpace: this.$api.isValue(this.lineSpace)? this.lineSpace :1.5,
					rowSpace: this.$api.isValue(this.rowSpace)? this.rowSpace :0.75,
					atom: this.atom ||"datetime",
					asideWidth: this.asideWidth ||4,
					template: this.template ||"common",
					iconBgColor: this.iconBgColor ||"#fff",
				}
			},
			keySetting(){
				if(this.$api.isObject(this.keys)){
					return Object.assign({}, this.dicKeys, this.keys)
				}
				return this.dicKeys
			},
			iconSetting(){
				if(this.$api.isObject(this.icon)){
					return Object.assign({}, this.dicIcon, this.icon)
				}
				return this.dicIcon
			},
			//组节点时间格式
			groupFormatSetting(){
				if(this.$api.isObject(this.groupFormat)){
					return Object.assign({}, this.dicGroupFormat, this.groupFormat)
				}
				return this.dicGroupFormat
			},
			//节点时间格式
			nodeFormatSetting(){
				if(this.$api.isObject(this.nodeFormat)){
					return Object.assign({}, this.dicNodeFormat, this.nodeFormat)
				}
				return this.dicNodeFormat
			},
			//轴线
			lineStyle(){
				let res ={};
				let asideWidth = this.timeAside? this.setting.asideWidth :0;
				switch(this.setting.mode){
					case "left":
						res["left"] = this.setting.lineSpace +asideWidth +"em"
						break;
					case "right":
						res["right"] = this.setting.lineSpace +asideWidth +"em"
						break;
					case "across":
						res["left"] = "50%"
						break;
				}
				res["top"] = (this.lineTop||0) +"px";
				res["bottom"] = (this.lineBottom||0) +"px";
				return this.$api.objToStr(res)
			},
			//组
			groupStyle(){
				let res ={};
				let asideWidth = this.timeAside? this.setting.asideWidth :0,
					lineSpace = this.setting.lineSpace;
				//如果内容为左右排列，宽度等于全宽减去线区域宽及对侧宽（如果开启对侧）。交错排列则等于50%全宽减去线区域宽
				switch(this.setting.mode){
					case "left":
						res["width"] = `calc(100% - ${lineSpace*2 +asideWidth}em)`
						res["left"] = `${lineSpace*2 +asideWidth}em`
						break;
					case "right":
						res["width"] = `calc(100% - ${lineSpace*2 +asideWidth}em)`
						res["right"] = `${lineSpace*2 +asideWidth}em`
						break;
					case "across":
						res["width"] = "100%"
				}
				return this.$api.objToStr(res)
			},
			//节点
			rowStyle(){
				let res ={};
				if(this.setting.mode=="across"){
					res["width"] = `calc(50% - ${this.setting.lineSpace}em)`
				}
				res["margin-bottom"] = this.setting.rowSpace +"em";
				return this.$api.objToStr(res)
			},
			//节点时间格式
			timeFormat(){
				let atom = this.setting.atom;
				return this.nodeFormatSetting[atom] ||atom
			},
		},
		methods: {
			//格式化时间
			dateFormat(value, format){
				if(!value){
					return ""
				}
				return this.$api.dateFormat(value, {format})
			},
			//遍历节点
			mapGroupItem(dataList, callback){
				for(let group of dataList){
					for(let item of group._children){
						callback(item)
					}
				}
			},
			//重构列表数据
			initList(newList){
				let target =[];
				let res =[];
				let timeKey = this.keySetting.itemTime;
				let groupMode = this.groupMode;
				if(!newList){
					this.inList = res;
					return
				}
				//统一组头结构
				if(!this.useGroup){
					let tempGroup = {};
					tempGroup[this.keySetting.groupChildren] = newList;
					target.push(tempGroup);
				}else{
					target = newList;
				}
				//初始化数据，补充结构
				for(let group of target){
					group["_name"] = group[this.keySetting.groupName] ||"";
					group["_children"] = group[this.keySetting.groupChildren] ||[];
					for(let item of group._children){
						item["_name"] = item[this.keySetting.itemName];
						//尝试转换日期，不能转换的返回原数据
						let time = item[timeKey];
						let t = typeof(time) != "number"? new Date(time).getTime() :time;
						item["_time"] = t ||time;
					}
				}
				//使用自定义组节点，原样返回，不依赖时间
				if(groupMode=="custom"){
					res = target
				}else{
					//使用预定义组节点必须根据节点时间排序
					//不使用组节点可不排序
					let sortType = groupMode? this.sort ||"asc" :this.sort;
					let tempRes =[];
					this.mapGroupItem(target, (item)=>{
						tempRes.push(item)
					});
					//按设置排序
					if(sortType){
						tempRes.sort(function(a,b){
							return sortType=="asc"? a[timeKey] - b[timeKey] : b[timeKey] - a[timeKey]
						})
					}
					//重新插入组节点
					if(groupMode){//预定义组节点
						let currFormat = this.groupFormatSetting[groupMode];
						let currTep, tempTep, itmeObj, currGroup;
						for(let item of tempRes){
							itmeObj = new Date(item._time);
							//构建比对值，比对值相同的在同一组
							switch(groupMode){
								case "year":
									tempTep = itmeObj.getFullYear() +""
									break;
								case "month":
									tempTep = `${itmeObj.getFullYear()}-${itmeObj.getMonth() +1}`
									break;
								case "day":
									tempTep = `${itmeObj.getFullYear()}-${itmeObj.getMonth() +1}-${itmeObj.getDate()}`
									break;
								case "hour":
									tempTep = `${itmeObj.getFullYear()}-${itmeObj.getMonth() +1}-${itmeObj.getDate()}-${itmeObj.getHours()}`
									break;
							}
							//没有当前比对值或比对值不同，创建新组
							if(!currTep||tempTep != currTep){
								currTep = tempTep;
								currGroup = {};
								currGroup["_name"] = this.$api.dateFormat(itmeObj, {format:currFormat});
								currGroup["_children"] = [item];
								res.push(currGroup);
							}else{//比对值相同，填入组
								currGroup["_children"].push(item)
							}
						}
					}else{
						let tempGroup = {};
						tempGroup["_name"] = "";
						tempGroup["_children"] = tempRes;
						res = [tempGroup];
					}
				}
				//补充数据
				for(let group of res){
					//组节点镂空
					if(!group.hasOwnProperty("plain")){
						group["plain"] = this.groupPlain ||false
					}
					//计算组标题长度
					group["_len"] = this.$api.lengthOf(group._name, {CN2:true}) /2;
					
					for(let i=0,len=group._children.length; i<len; i++){
						let item = group._children[i];
						//计算对齐
						if(this.setting.mode == "across"){
							//true为右侧
							item["_align"] = item.hasOwnProperty("align")? !!item.align :i%2==1
						}
						//节点镂空，默认镂空
						if(!item.hasOwnProperty("plain")){
							item["plain"] = true
						}
					}
				}
				this.inList = res;
			},
			//内容事件传递
			onEvent(e){
				this.$emit("event", e)
			},
			//节点点击事件
			itemClick(item){
				this.$emit("click", {
					type: "item",
					tiem: item
				})
			},
			groupClick(group){
				this.$emit("click", {
					type: "group",
					tiem: group
				})
			},
			//设置轴线高不超过第一个和最后一个节点
			setLine(){
				//获取行高
				const views = uni.createSelectorQuery().in(this);
				views.selectAll(`.vi_timeline_row`)
					.fields({
						size: true
					}, (data) => {
						if(data.length){
							if(!this.inList[0]._name){//第一个组没有组节点
								this.lineTop = data[0].height /2
							}
							this.lineBottom = data[data.length-1].height /2
						}
					})
					.exec()
			},
			//外部用，刷新
			refresh(){
				this.setLine()
			}
		},
		watch:{
			"dataList":{
				immediate:true,
				handler(newVal){
					this.initList(newVal)
				}
			}
		},
		mounted() {
			//获取轴线高度，注意如果外层嵌套popup父组件，微信小程序会在页面渲染周期立即执行组件渲染周期，无视父组件的v-if，导致获取高度失效。需在父组件打开结束时用refs调用高度获取方法
			this.setLine()
		}
	}
</script>

<style lang="scss" scoped>
.vi_timeline{
	position: relative;
	.vi_timeline_line{
		content: '';
		width: 0;
		border: 1px solid #eee;
		position: absolute;
		top: 0;
		bottom: 0;
	}
	.vi_timeline_group{
		position: relative;
		clear: both;
	}
	.vi_timeline_groupHead{
		display: inline-block;
		position: relative;
		clear: both;
	}
	.vi_timeline_groupHead_text{
		padding: 0.15em 0.5em;
	}
	.vi_timeline_row{
		position: relative;
		clear: both;
	}
	.vi_timeline_content{
		min-height: 1.5em;
	}
	.vi_timeline_rowIcon{
		position: absolute;
		top: 50%;
		width: 1.2em;
		height: 1.4em;
		display: flex;
		align-items: center;
	}
	.vi_timeline_aside{
		position: absolute;
		top: 50%;
		height: 1.4em;
		color: $vi-txtColor-tips;
		.vi_timeline_text{
			font-size: $vi-fontSize-sm;
		}
	}
	.vi_timeline_content{
		position: relative;
	}
	// 居左模式
	.vi_timeline_left{
		&.vi_timeline_line{
			transform: translateX(-50%);
		}
		.vi_timeline_groupHead{
			transform: translateX(-50%);
			&.vi_timeline_overflow{
				transform: translateX(0);
			}
		}
		.vi_timeline_rowIcon{
			transform: translate(-50%, -50%);
		}
		.vi_timeline_aside{
			transform: translate(-100%, -50%);
		}
	}
	// 居右模式
	.vi_timeline_right{
		&.vi_timeline_line{
			transform: translateX(50%);
		}
		&.vi_timeline_group{
			float: right;
		}
		.vi_timeline_groupHead{
			float: right;
			transform: translateX(50%);
			&.vi_timeline_overflow{
				transform: translateX(0);
			}
		}
		.vi_timeline_rowIcon{
			transform: translate(50%, -50%);
		}
		.vi_timeline_aside{
			transform: translate(100%, -50%);
		}
	}
	// 交错模式
	.vi_timeline_across{
		&.vi_timeline_line{
			transform: translateX(-50%);
		}
		.vi_timeline_groupHead{
			left: 50% !important;
			transform: translateX(-50%);
		}
		.vi_acrossLeft{
			.vi_timeline_rowIcon{
				transform: translate(50%, -50%);
			}
			.vi_timeline_aside{
				transform: translate(100%, -50%);
			}
		}
		.vi_acrossRight{
			float: right;
			.vi_timeline_rowIcon{
				transform: translate(-50%, -50%);
			}
			.vi_timeline_aside{
				transform: translate(-100%, -50%);
			}
		}
	}
}
.vi_clearFix::after{content:""; clear:both; height: 0; display: block; visibility: hidden; line-height: 0;}
</style>
